Repository navigation
refactor(utils): simplify formatting error handling - #189
yugesh-ganipudi wants to merge 1 commit into
Conversation
WalkthroughAdds standardized, message-based error handling across multiple provider modules, introducing early-return branches for API key, rate limit, connection, and server errors. Fallback messages are unified per provider. Control flow now derives a single message string and routes to provider-specific normalized errors. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Provider
participant UpstreamAPI as Upstream API
Client->>Provider: request(model, payload)
Provider->>UpstreamAPI: send request
UpstreamAPI--x Provider: error (status/message)
rect rgba(230,240,255,0.5)
note over Provider: Derive message string once
alt API key invalid
Provider-->>Client: Normalized "Invalid API key/endpoint" error
else Rate limited (incl. 429)
Provider-->>Client: Normalized "Rate limit exceeded" error
else Connection issue
Provider-->>Client: Normalized "API connection error" message
else Server error (5xx)
Provider-->>Client: Normalized "API server error" message
else Provider-specific/fallback
Provider-->>Client: "<Provider> Error: {message}"
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib/providers/googleVertex.ts (1)
365-371: Bug: streamText result is not awaited (passes a Promise to createTextStream).Await the streamText call before transforming the stream.
Apply this diff:
- const result = streamText(streamOptions); + const result = await streamText(streamOptions);
🧹 Nitpick comments (9)
src/lib/providers/googleVertex.ts (3)
423-446: Normalize error matching and honor HTTP status codes.Use a lowercased message and check optional status fields to make detection robust. Also include ETIMEDOUT/timeout and common gateway phrases.
Apply this diff:
- if ( - message.includes("ECONNRESET") || - message.includes("ENOTFOUND") || - message.includes("ECONNREFUSED") || - message.includes("network") || - message.includes("connection") - ) { + const lowerMessage = message.toLowerCase(); + const status = + (errorRecord as { status?: number })?.status ?? + (errorRecord as { response?: { status?: number } })?.response?.status; + + if ( + lowerMessage.includes("econnreset") || + lowerMessage.includes("enotfound") || + lowerMessage.includes("econnrefused") || + lowerMessage.includes("etimedout") || + lowerMessage.includes("network") || + lowerMessage.includes("connection") || + lowerMessage.includes("timeout") + ) { return new Error( "Google Vertex AI API connection error. Please check your internet connection and try again.", ); } - if ( - message.includes("500") || - message.includes("502") || - message.includes("503") || - message.includes("504") || - message.includes("server error") - ) { + if ( + status === 500 || + status === 502 || + status === 503 || + status === 504 || + lowerMessage.includes("server error") || + lowerMessage.includes("bad gateway") || + lowerMessage.includes("gateway timeout") + ) { return new Error( "Google Vertex AI API server error. Please try again in a few moments.", ); }
177-183: Doc nit: comment says WeakMap but a Map is used.Align the comment with the implementation to avoid confusion.
Apply this diff:
- // Memory-managed cache for model configuration lookups to avoid repeated calls - // Uses WeakMap for automatic cleanup and bounded LRU for recently used models + // Memory-managed cache for model configuration lookups to avoid repeated calls + // Uses a bounded LRU strategy with Map for recently used models
418-422: Optional: lowercase once at source.For consistency with other providers and tests, compute a lowercase variant once and reuse in all checks.
src/lib/providers/openAI.ts (2)
97-131: Harden error detection: lowercase matching and status checks.Normalize the message and include HTTP status where available for rate limit, connection, and server errors.
Apply this diff:
- if ( - message.includes("rate limit") || - message.includes("rate_limit_exceeded") || - message.includes("429") - ) { + const lower = message.toLowerCase(); + const status = + (errorObj as { status?: number })?.status ?? + (errorObj as { response?: { status?: number } })?.response?.status; + + if ( + status === 429 || + lower.includes("rate limit") || + lower.includes("rate_limit_exceeded") || + lower.includes("429") + ) { return new Error("OpenAI rate limit exceeded. Please try again later."); } - // Handle connection errors - if ( - message.includes("ECONNRESET") || - message.includes("ENOTFOUND") || - message.includes("ECONNREFUSED") || - message.includes("network") || - message.includes("connection") - ) { + // Handle connection errors + if ( + lower.includes("econnreset") || + lower.includes("enotfound") || + lower.includes("econnrefused") || + lower.includes("etimedout") || + lower.includes("network") || + lower.includes("connection") || + lower.includes("timeout") + ) { return new Error( "OpenAI API connection error. Please check your internet connection and try again.", ); } - // Handle server errors - if ( - message.includes("500") || - message.includes("502") || - message.includes("503") || - message.includes("504") || - message.includes("server error") - ) { + // Handle server errors + if ( + status === 500 || + status === 502 || + status === 503 || + status === 504 || + lower.includes("server error") || + lower.includes("bad gateway") || + lower.includes("gateway timeout") + ) { return new Error( "OpenAI API server error. Please try again in a few moments.", ); } - return new Error(`OpenAI Error: ${message}`); + return new Error(`OpenAI Error: ${message}`);
82-87: Optional: precompute lowercasemessage
Computeconst lowerMessage = message.toLowerCase()once after derivingmessageand uselowerMessage.includes(…)in the subsequent error-handling checks.src/lib/providers/mistral.ts (1)
173-212: Harden error detection: lowercase matching and status checks.Normalize the message and include HTTP status where available; also lower-case the model-not-found check.
Apply this diff:
- if ( - message.includes("rate limit") || - message.includes("rate_limit_exceeded") || - message.includes("429") - ) { + const lower = message.toLowerCase(); + const status = + (errorRecord as { status?: number })?.status ?? + (errorRecord as { response?: { status?: number } })?.response?.status; + + if ( + status === 429 || + lower.includes("rate limit") || + lower.includes("rate_limit_exceeded") || + lower.includes("429") + ) { return new Error("Mistral rate limit exceeded. Please try again later."); } - if ( - message.includes("ECONNRESET") || - message.includes("ENOTFOUND") || - message.includes("ECONNREFUSED") || - message.includes("network") || - message.includes("connection") - ) { + if ( + lower.includes("econnreset") || + lower.includes("enotfound") || + lower.includes("econnrefused") || + lower.includes("etimedout") || + lower.includes("network") || + lower.includes("connection") || + lower.includes("timeout") + ) { return new Error( "Mistral API connection error. Please check your internet connection and try again.", ); } - if ( - message.includes("500") || - message.includes("502") || - message.includes("503") || - message.includes("504") || - message.includes("server error") - ) { + if ( + status === 500 || + status === 502 || + status === 503 || + status === 504 || + lower.includes("server error") || + lower.includes("bad gateway") || + lower.includes("gateway timeout") + ) { return new Error( "Mistral API server error. Please try again in a few moments.", ); } - if (message.includes("model") && message.includes("not found")) { + if (lower.includes("model") && lower.includes("not found")) { return new Error( `Mistral model '${this.modelName}' not found.\n\nSuggestions:\n1. Check model name spelling\n2. Ensure you have access to the model`, ); } - return new Error(`Mistral Error: ${message}`); + return new Error(`Mistral Error: ${message}`);src/lib/providers/amazonBedrock.ts (2)
163-196: Harden error detection: lowercase matching and status checks (AWS often sets $metadata.httpStatusCode).Normalize the message and read status from multiple potential locations.
Apply this diff:
- if ( - errorMessage.includes("rate limit") || - errorMessage.includes("throttling") || - errorMessage.includes("429") - ) { + const lower = errorMessage.toLowerCase(); + const status = + (error as { status?: number })?.status ?? + (error as { $metadata?: { httpStatusCode?: number } })?.$metadata?.httpStatusCode ?? + (error as { response?: { status?: number } })?.response?.status; + + if ( + status === 429 || + lower.includes("rate limit") || + lower.includes("throttling") || + lower.includes("too many requests") || + lower.includes("429") + ) { return new Error( "Amazon Bedrock rate limit exceeded. Please try again later.", ); } - if ( - errorMessage.includes("ECONNRESET") || - errorMessage.includes("ENOTFOUND") || - errorMessage.includes("ECONNREFUSED") || - errorMessage.includes("network") || - errorMessage.includes("connection") - ) { + if ( + lower.includes("econnreset") || + lower.includes("enotfound") || + lower.includes("econnrefused") || + lower.includes("etimedout") || + lower.includes("network") || + lower.includes("connection") || + lower.includes("timeout") + ) { return new Error( "Amazon Bedrock API connection error. Please check your internet connection and try again.", ); } - if ( - errorMessage.includes("500") || - errorMessage.includes("502") || - errorMessage.includes("503") || - errorMessage.includes("504") || - errorMessage.includes("server error") - ) { + if ( + status === 500 || + status === 502 || + status === 503 || + status === 504 || + lower.includes("server error") || + lower.includes("bad gateway") || + lower.includes("gateway timeout") + ) { return new Error( "Amazon Bedrock API server error. Please try again in a few moments.", ); }
122-138: Consistency: add timeout/abortSignal to Bedrock streaming like other providers.Adopt the same timeout controller pattern to avoid hanging requests.
Apply this diff:
- try { - this.validateStreamOptions(options); + try { + this.validateStreamOptions(options); + const timeout = this.getTimeout(options); + const timeoutController = createTimeoutController( + timeout, + this.providerName, + "stream", + ); // Build message array from options const messages = buildMessagesArray(options); - const result = await streamText({ + const result = await streamText({ model: this.model, messages: messages, maxTokens: options.maxTokens || DEFAULT_MAX_TOKENS, temperature: options.temperature, + abortSignal: timeoutController?.controller.signal, }); + timeoutController?.cleanup();src/lib/providers/anthropic.ts (1)
88-140: Consolidate to lowercase message and include HTTP status checks.Improves robustness and aligns with other providers.
Apply this diff:
- const message = - typeof errorRecord?.message === "string" - ? errorRecord.message - : "Unknown error"; + const message = + typeof errorRecord?.message === "string" + ? errorRecord.message + : "Unknown error"; + const lowerMessage = message.toLowerCase(); + const status = + (errorRecord as { status?: number })?.status ?? + (errorRecord as { response?: { status?: number } })?.response?.status; @@ - if ( - message.includes("API_KEY_INVALID") || - message.includes("Invalid API key") - ) { + if ( + lowerMessage.includes("api_key_invalid") || + lowerMessage.includes("invalid api key") + ) { return new Error( "Invalid Anthropic API key. Please check your ANTHROPIC_API_KEY environment variable.", ); } @@ - if ( - message.includes("rate limit") || - message.includes("too_many_requests") || - message.includes("429") - ) { + if ( + status === 429 || + lowerMessage.includes("rate limit") || + lowerMessage.includes("too_many_requests") || + lowerMessage.includes("429") + ) { return new Error( "Anthropic rate limit exceeded. Please try again later.", ); } @@ - if ( - message.includes("ECONNRESET") || - message.includes("ENOTFOUND") || - message.includes("ECONNREFUSED") || - message.includes("network") || - message.includes("connection") - ) { + if ( + lowerMessage.includes("econnreset") || + lowerMessage.includes("enotfound") || + lowerMessage.includes("econnrefused") || + lowerMessage.includes("etimedout") || + lowerMessage.includes("network") || + lowerMessage.includes("connection") || + lowerMessage.includes("timeout") + ) { return new Error( "Anthropic API connection error. Please check your internet connection and try again.", ); } @@ - if ( - message.includes("500") || - message.includes("502") || - message.includes("503") || - message.includes("504") || - message.includes("server error") - ) { + if ( + status === 500 || + status === 502 || + status === 503 || + status === 504 || + lowerMessage.includes("server error") || + lowerMessage.includes("bad gateway") || + lowerMessage.includes("gateway timeout") + ) { return new Error( "Anthropic API server error. Please try again in a few moments.", ); } - return new Error(`Anthropic Error: ${message}`); + return new Error(`Anthropic Error: ${message}`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/lib/providers/amazonBedrock.ts(1 hunks)src/lib/providers/anthropic.ts(4 hunks)src/lib/providers/azureOpenai.ts(1 hunks)src/lib/providers/googleAiStudio.ts(1 hunks)src/lib/providers/googleVertex.ts(1 hunks)src/lib/providers/litellm.ts(1 hunks)src/lib/providers/mistral.ts(1 hunks)src/lib/providers/openAI.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-17T17:55:15.261Z
Learnt from: RajuSudhar
PR: juspay/neurolink#173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.
Applied to files:
src/lib/providers/litellm.tssrc/lib/providers/openAI.tssrc/lib/providers/googleAiStudio.ts
📚 Learning: 2025-09-02T13:50:42.770Z
Learnt from: YasmeenOgo
PR: juspay/neurolink#145
File: src/lib/core/types.ts:0-0
Timestamp: 2025-09-02T13:50:42.770Z
Learning: The APIVersions enum in src/lib/core/types.ts now contains comprehensive API version constants for all major AI providers: Azure OpenAI (latest, stable, legacy), OpenAI (current, beta), Google AI (current, beta), and Anthropic (current). This centralization helps avoid API version drift across the codebase.
Applied to files:
src/lib/providers/openAI.tssrc/lib/providers/azureOpenai.ts
🧬 Code graph analysis (7)
src/lib/providers/anthropic.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
src/lib/providers/litellm.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
src/lib/providers/openAI.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
src/lib/providers/googleAiStudio.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
src/lib/providers/googleVertex.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
src/lib/providers/mistral.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
src/lib/providers/azureOpenai.ts (1)
neurolink-demo/test-providers.js (1)
message(291-291)
| const message = | ||
| errorObj?.message && typeof errorObj.message === "string" | ||
| ? errorObj.message | ||
| : "Unknown error"; | ||
| return new Error(`Azure OpenAI error: ${message}`); | ||
|
|
||
| if (message.includes("401")) { | ||
| return new Error("Invalid Azure OpenAI API key or endpoint."); | ||
| } | ||
|
|
||
| if ( | ||
| message.includes("rate limit") || | ||
| message.includes("rate_limit_exceeded") || | ||
| message.includes("429") | ||
| ) { | ||
| return new Error( | ||
| "Azure OpenAI rate limit exceeded. Please try again later.", | ||
| ); | ||
| } | ||
|
|
||
| // Handle connection errors | ||
| if ( | ||
| message.includes("ECONNRESET") || | ||
| message.includes("ENOTFOUND") || | ||
| message.includes("ECONNREFUSED") || | ||
| message.includes("network") || | ||
| message.includes("connection") | ||
| ) { | ||
| return new Error( | ||
| "Azure OpenAI API connection error. Please check your internet connection and try again.", | ||
| ); | ||
| } | ||
|
|
||
| // Handle server errors | ||
| if ( | ||
| message.includes("500") || | ||
| message.includes("502") || | ||
| message.includes("503") || | ||
| message.includes("504") || | ||
| message.includes("server error") | ||
| ) { | ||
| return new Error( | ||
| "Azure OpenAI API server error. Please try again in a few moments.", | ||
| ); | ||
| } | ||
|
|
||
| return new Error(`Azure OpenAI Error: ${message}`); | ||
| } |
There was a problem hiding this comment.
Case-sensitive comparisons regress the new Azure error handling
Azure responses (and node-fetch errors) routinely capitalize phrases like "Network Error" or "Server Error". Because message is no longer lowercased, checks such as message.includes("network")/"server error" now miss, so we fall back to the generic "Azure OpenAI Error: …" for common scenarios that previously produced tailored guidance.
Lowercase the message once for comparison while keeping the original text for the returned error.
- const message =
- errorObj?.message && typeof errorObj.message === "string"
- ? errorObj.message
- : "Unknown error";
+ const rawMessage =
+ errorObj?.message && typeof errorObj.message === "string"
+ ? errorObj.message
+ : "Unknown error";
+ const normalizedMessage = rawMessage.toLowerCase();
- if (message.includes("401")) {
+ if (normalizedMessage.includes("401")) {
return new Error("Invalid Azure OpenAI API key or endpoint.");
}
if (
- message.includes("rate limit") ||
- message.includes("rate_limit_exceeded") ||
- message.includes("429")
+ normalizedMessage.includes("rate limit") ||
+ normalizedMessage.includes("rate_limit_exceeded") ||
+ normalizedMessage.includes("429")
) {
return new Error(
"Azure OpenAI rate limit exceeded. Please try again later.",
);
}
// Handle connection errors
if (
- message.includes("ECONNRESET") ||
- message.includes("ENOTFOUND") ||
- message.includes("ECONNREFUSED") ||
- message.includes("network") ||
- message.includes("connection")
+ normalizedMessage.includes("econnreset") ||
+ normalizedMessage.includes("enotfound") ||
+ normalizedMessage.includes("econnrefused") ||
+ normalizedMessage.includes("network") ||
+ normalizedMessage.includes("connection")
) {
return new Error(
"Azure OpenAI API connection error. Please check your internet connection and try again.",
);
}
// Handle server errors
if (
- message.includes("500") ||
- message.includes("502") ||
- message.includes("503") ||
- message.includes("504") ||
- message.includes("server error")
+ normalizedMessage.includes("500") ||
+ normalizedMessage.includes("502") ||
+ normalizedMessage.includes("503") ||
+ normalizedMessage.includes("504") ||
+ normalizedMessage.includes("server error")
) {
return new Error(
"Azure OpenAI API server error. Please try again in a few moments.",
);
}
- return new Error(`Azure OpenAI Error: ${message}`);
+ return new Error(`Azure OpenAI Error: ${rawMessage}`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const message = | |
| errorObj?.message && typeof errorObj.message === "string" | |
| ? errorObj.message | |
| : "Unknown error"; | |
| return new Error(`Azure OpenAI error: ${message}`); | |
| if (message.includes("401")) { | |
| return new Error("Invalid Azure OpenAI API key or endpoint."); | |
| } | |
| if ( | |
| message.includes("rate limit") || | |
| message.includes("rate_limit_exceeded") || | |
| message.includes("429") | |
| ) { | |
| return new Error( | |
| "Azure OpenAI rate limit exceeded. Please try again later.", | |
| ); | |
| } | |
| // Handle connection errors | |
| if ( | |
| message.includes("ECONNRESET") || | |
| message.includes("ENOTFOUND") || | |
| message.includes("ECONNREFUSED") || | |
| message.includes("network") || | |
| message.includes("connection") | |
| ) { | |
| return new Error( | |
| "Azure OpenAI API connection error. Please check your internet connection and try again.", | |
| ); | |
| } | |
| // Handle server errors | |
| if ( | |
| message.includes("500") || | |
| message.includes("502") || | |
| message.includes("503") || | |
| message.includes("504") || | |
| message.includes("server error") | |
| ) { | |
| return new Error( | |
| "Azure OpenAI API server error. Please try again in a few moments.", | |
| ); | |
| } | |
| return new Error(`Azure OpenAI Error: ${message}`); | |
| } | |
| const rawMessage = | |
| errorObj?.message && typeof errorObj.message === "string" | |
| ? errorObj.message | |
| : "Unknown error"; | |
| const normalizedMessage = rawMessage.toLowerCase(); | |
| if (normalizedMessage.includes("401")) { | |
| return new Error("Invalid Azure OpenAI API key or endpoint."); | |
| } | |
| if ( | |
| normalizedMessage.includes("rate limit") || | |
| normalizedMessage.includes("rate_limit_exceeded") || | |
| normalizedMessage.includes("429") | |
| ) { | |
| return new Error( | |
| "Azure OpenAI rate limit exceeded. Please try again later.", | |
| ); | |
| } | |
| // Handle connection errors | |
| if ( | |
| normalizedMessage.includes("econnreset") || | |
| normalizedMessage.includes("enotfound") || | |
| normalizedMessage.includes("econnrefused") || | |
| normalizedMessage.includes("network") || | |
| normalizedMessage.includes("connection") | |
| ) { | |
| return new Error( | |
| "Azure OpenAI API connection error. Please check your internet connection and try again.", | |
| ); | |
| } | |
| // Handle server errors | |
| if ( | |
| normalizedMessage.includes("500") || | |
| normalizedMessage.includes("502") || | |
| normalizedMessage.includes("503") || | |
| normalizedMessage.includes("504") || | |
| normalizedMessage.includes("server error") | |
| ) { | |
| return new Error( | |
| "Azure OpenAI API server error. Please try again in a few moments.", | |
| ); | |
| } | |
| return new Error(`Azure OpenAI Error: ${rawMessage}`); |
🤖 Prompt for AI Agents
In src/lib/providers/azureOpenai.ts around lines 82 to 128, the error text
checks are case-sensitive because the message isn't lowercased; as a result
capitalized Azure/node-fetch messages like "Network Error" or "Server Error"
miss the specific branches. Create a lowercase copy of the message (e.g.,
messageLower = message.toLowerCase()) and use messageLower for all includes()
comparisons (401, rate limit, network/connection, server errors), but keep using
the original message when constructing the returned Error so the original casing
and details are preserved.
| const message = | ||
| typeof errorRecord?.message === "string" | ||
| ? errorRecord.message | ||
| : "Unknown error"; | ||
|
|
||
| if (message.includes("API_KEY_INVALID")) { | ||
| return new Error( | ||
| "Invalid Google AI API key. Please check your GOOGLE_AI_API_KEY environment variable.", | ||
| ); | ||
| } | ||
|
|
||
| if (message.includes("RATE_LIMIT_EXCEEDED")) { | ||
| return new Error( | ||
| "Google AI rate limit exceeded. Please try again later.", | ||
| ); | ||
| } | ||
|
|
||
| if ( | ||
| typeof errorRecord?.message === "string" && | ||
| errorRecord.message.includes("API_KEY_INVALID") | ||
| message.includes("ECONNRESET") || | ||
| message.includes("ENOTFOUND") || | ||
| message.includes("ECONNREFUSED") || | ||
| message.includes("network") || | ||
| message.includes("connection") | ||
| ) { | ||
| return new Error( | ||
| "Invalid Google AI API key. Please check your GOOGLE_AI_API_KEY environment variable.", | ||
| "Google AI API connection error. Please check your internet connection and try again.", | ||
| ); | ||
| } | ||
|
|
||
| if ( | ||
| typeof errorRecord?.message === "string" && | ||
| errorRecord.message.includes("RATE_LIMIT_EXCEEDED") | ||
| message.includes("500") || | ||
| message.includes("502") || | ||
| message.includes("503") || | ||
| message.includes("504") || | ||
| message.includes("server error") | ||
| ) { | ||
| return new Error( | ||
| "Google AI rate limit exceeded. Please try again later.", | ||
| "Google AI API server error. Please try again in a few moments.", | ||
| ); | ||
| } | ||
|
|
||
| const message = | ||
| typeof errorRecord?.message === "string" | ||
| ? errorRecord.message | ||
| : "Unknown error"; | ||
| return new Error(`Google AI error: ${message}`); | ||
| return new Error(`Google AI Error: ${message}`); | ||
| } |
There was a problem hiding this comment.
Case-sensitive matching breaks new error buckets
Similar to the LiteLLM refactor, we now rely on raw message strings, but most checks compare lowercase fragments like "network" / "server error" against messages that come back as "Network error" / "Server Error". These matches no longer fire, causing us to fall back to the generic "Google AI Error: …" and losing the standardized guidance.
Normalize the message once for comparisons while preserving the original text for the final wrapper.
- const message =
- typeof errorRecord?.message === "string"
- ? errorRecord.message
- : "Unknown error";
+ const rawMessage =
+ typeof errorRecord?.message === "string"
+ ? errorRecord.message
+ : "Unknown error";
+ const normalizedMessage = rawMessage.toLowerCase();
- if (message.includes("API_KEY_INVALID")) {
+ if (normalizedMessage.includes("api_key_invalid")) {
return new Error(
"Invalid Google AI API key. Please check your GOOGLE_AI_API_KEY environment variable.",
);
}
- if (message.includes("RATE_LIMIT_EXCEEDED")) {
+ if (normalizedMessage.includes("rate_limit_exceeded")) {
return new Error(
"Google AI rate limit exceeded. Please try again later.",
);
}
if (
- message.includes("ECONNRESET") ||
- message.includes("ENOTFOUND") ||
- message.includes("ECONNREFUSED") ||
- message.includes("network") ||
- message.includes("connection")
+ normalizedMessage.includes("econnreset") ||
+ normalizedMessage.includes("enotfound") ||
+ normalizedMessage.includes("econnrefused") ||
+ normalizedMessage.includes("network") ||
+ normalizedMessage.includes("connection")
) {
return new Error(
"Google AI API connection error. Please check your internet connection and try again.",
);
}
if (
- message.includes("500") ||
- message.includes("502") ||
- message.includes("503") ||
- message.includes("504") ||
- message.includes("server error")
+ normalizedMessage.includes("500") ||
+ normalizedMessage.includes("502") ||
+ normalizedMessage.includes("503") ||
+ normalizedMessage.includes("504") ||
+ normalizedMessage.includes("server error")
) {
return new Error(
"Google AI API server error. Please try again in a few moments.",
);
}
- return new Error(`Google AI Error: ${message}`);
+ return new Error(`Google AI Error: ${rawMessage}`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const message = | |
| typeof errorRecord?.message === "string" | |
| ? errorRecord.message | |
| : "Unknown error"; | |
| if (message.includes("API_KEY_INVALID")) { | |
| return new Error( | |
| "Invalid Google AI API key. Please check your GOOGLE_AI_API_KEY environment variable.", | |
| ); | |
| } | |
| if (message.includes("RATE_LIMIT_EXCEEDED")) { | |
| return new Error( | |
| "Google AI rate limit exceeded. Please try again later.", | |
| ); | |
| } | |
| if ( | |
| typeof errorRecord?.message === "string" && | |
| errorRecord.message.includes("API_KEY_INVALID") | |
| message.includes("ECONNRESET") || | |
| message.includes("ENOTFOUND") || | |
| message.includes("ECONNREFUSED") || | |
| message.includes("network") || | |
| message.includes("connection") | |
| ) { | |
| return new Error( | |
| "Invalid Google AI API key. Please check your GOOGLE_AI_API_KEY environment variable.", | |
| "Google AI API connection error. Please check your internet connection and try again.", | |
| ); | |
| } | |
| if ( | |
| typeof errorRecord?.message === "string" && | |
| errorRecord.message.includes("RATE_LIMIT_EXCEEDED") | |
| message.includes("500") || | |
| message.includes("502") || | |
| message.includes("503") || | |
| message.includes("504") || | |
| message.includes("server error") | |
| ) { | |
| return new Error( | |
| "Google AI rate limit exceeded. Please try again later.", | |
| "Google AI API server error. Please try again in a few moments.", | |
| ); | |
| } | |
| const message = | |
| typeof errorRecord?.message === "string" | |
| ? errorRecord.message | |
| : "Unknown error"; | |
| return new Error(`Google AI error: ${message}`); | |
| return new Error(`Google AI Error: ${message}`); | |
| } | |
| const rawMessage = | |
| typeof errorRecord?.message === "string" | |
| ? errorRecord.message | |
| : "Unknown error"; | |
| const normalizedMessage = rawMessage.toLowerCase(); | |
| if (normalizedMessage.includes("api_key_invalid")) { | |
| return new Error( | |
| "Invalid Google AI API key. Please check your GOOGLE_AI_API_KEY environment variable.", | |
| ); | |
| } | |
| if (normalizedMessage.includes("rate_limit_exceeded")) { | |
| return new Error( | |
| "Google AI rate limit exceeded. Please try again later.", | |
| ); | |
| } | |
| if ( | |
| normalizedMessage.includes("econnreset") || | |
| normalizedMessage.includes("enotfound") || | |
| normalizedMessage.includes("econnrefused") || | |
| normalizedMessage.includes("network") || | |
| normalizedMessage.includes("connection") | |
| ) { | |
| return new Error( | |
| "Google AI API connection error. Please check your internet connection and try again.", | |
| ); | |
| } | |
| if ( | |
| normalizedMessage.includes("500") || | |
| normalizedMessage.includes("502") || | |
| normalizedMessage.includes("503") || | |
| normalizedMessage.includes("504") || | |
| normalizedMessage.includes("server error") | |
| ) { | |
| return new Error( | |
| "Google AI API server error. Please try again in a few moments.", | |
| ); | |
| } | |
| return new Error(`Google AI Error: ${rawMessage}`); |
🤖 Prompt for AI Agents
In src/lib/providers/googleAiStudio.ts around lines 77 to 119, the error
handling compares fragments in a case-sensitive way against the raw message so
checks like "network" and "server error" miss matches like "Network error" or
"Server Error"; normalize the message once (e.g., to lower-case) for all
substring comparisons while keeping the original message intact for the final
Error text, and update each if condition to test the normalized string instead
of the raw message.
| const message = | ||
| typeof errorRecord?.message === "string" | ||
| ? errorRecord.message | ||
| : "Unknown error"; | ||
|
|
||
| if ( | ||
| errorRecord?.name === "TimeoutError" || | ||
| (typeof errorRecord?.message === "string" && | ||
| errorRecord.message.includes("Timeout")) | ||
| message.includes("ECONNREFUSED") || | ||
| message.includes("Failed to fetch") || | ||
| message.includes("ECONNRESET") || | ||
| message.includes("ENOTFOUND") | ||
| ) { | ||
| return new Error( | ||
| `LiteLLM request timed out: ${errorRecord?.message || "Unknown timeout"}`, | ||
| `LiteLLM proxy server not available. Please start the LiteLLM proxy server at ${ | ||
| process.env.LITELLM_BASE_URL || "http://localhost:4000" | ||
| }`, | ||
| ); | ||
| } | ||
| if (typeof errorRecord?.message === "string") { | ||
| if ( | ||
| errorRecord.message.includes("ECONNREFUSED") || | ||
| errorRecord.message.includes("Failed to fetch") | ||
| ) { | ||
| return new Error( | ||
| "LiteLLM proxy server not available. Please start the LiteLLM proxy server at " + | ||
| `${process.env.LITELLM_BASE_URL || "http://localhost:4000"}`, | ||
| ); | ||
| } | ||
|
|
||
| if ( | ||
| errorRecord.message.includes("API_KEY_INVALID") || | ||
| errorRecord.message.includes("Invalid API key") | ||
| ) { | ||
| return new Error( | ||
| "Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.", | ||
| ); | ||
| } | ||
| if ( | ||
| message.includes("API_KEY_INVALID") || | ||
| message.includes("Invalid API key") | ||
| ) { | ||
| return new Error( | ||
| "Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.", | ||
| ); | ||
| } | ||
|
|
||
| if (errorRecord.message.includes("rate limit")) { | ||
| return new Error( | ||
| "LiteLLM rate limit exceeded. Please try again later.", | ||
| ); | ||
| } | ||
| if ( | ||
| message.includes("rate limit") || | ||
| message.includes("rate_limit_exceeded") || | ||
| message.includes("429") | ||
| ) { | ||
| return new Error("LiteLLM rate limit exceeded. Please try again later."); | ||
| } | ||
|
|
||
| if ( | ||
| errorRecord.message.includes("model") && | ||
| errorRecord.message.includes("not found") | ||
| ) { | ||
| return new Error( | ||
| `Model '${this.modelName}' not available in LiteLLM proxy. ` + | ||
| "Please check your LiteLLM configuration and ensure the model is configured.", | ||
| ); | ||
| } | ||
| if ( | ||
| message.includes("500") || | ||
| message.includes("502") || | ||
| message.includes("503") || | ||
| message.includes("504") || | ||
| message.includes("server error") | ||
| ) { | ||
| return new Error( | ||
| "LiteLLM proxy server error. Please check the proxy server logs for more details.", | ||
| ); | ||
| } | ||
|
|
||
| return new Error( | ||
| `LiteLLM error: ${errorRecord?.message || "Unknown error"}`, | ||
| ); | ||
| return new Error(`LiteLLM Error: ${message}`); | ||
| } |
There was a problem hiding this comment.
Restore case-insensitive error routing
message is left in its original casing, yet most of the subsequent includes(...) checks use lowercase tokens (e.g., "rate limit", "server error"). With real LiteLLM responses like "Rate limit exceeded" or "Server Error", those checks now silently miss and we fall back to the generic "LiteLLM Error: …" wrapper, undoing the standardized messaging this refactor set out to provide.
Normalize the string once and perform the matches against that lowercase representation while keeping the original message for the final wrapper.
- const message =
- typeof errorRecord?.message === "string"
- ? errorRecord.message
- : "Unknown error";
+ const rawMessage =
+ typeof errorRecord?.message === "string"
+ ? errorRecord.message
+ : "Unknown error";
+ const normalizedMessage = rawMessage.toLowerCase();
- if (
- message.includes("ECONNREFUSED") ||
- message.includes("Failed to fetch") ||
- message.includes("ECONNRESET") ||
- message.includes("ENOTFOUND")
+ if (
+ normalizedMessage.includes("econnrefused") ||
+ normalizedMessage.includes("failed to fetch") ||
+ normalizedMessage.includes("econnreset") ||
+ normalizedMessage.includes("enotfound")
) {
return new Error(
`LiteLLM proxy server not available. Please start the LiteLLM proxy server at ${
process.env.LITELLM_BASE_URL || "http://localhost:4000"
}`,
);
}
- if (
- message.includes("API_KEY_INVALID") ||
- message.includes("Invalid API key")
- ) {
+ if (
+ normalizedMessage.includes("api_key_invalid") ||
+ normalizedMessage.includes("invalid api key")
+ ) {
return new Error(
"Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.",
);
}
- if (
- message.includes("rate limit") ||
- message.includes("rate_limit_exceeded") ||
- message.includes("429")
- ) {
+ if (
+ normalizedMessage.includes("rate limit") ||
+ normalizedMessage.includes("rate_limit_exceeded") ||
+ normalizedMessage.includes("429")
+ ) {
return new Error("LiteLLM rate limit exceeded. Please try again later.");
}
- if (
- message.includes("500") ||
- message.includes("502") ||
- message.includes("503") ||
- message.includes("504") ||
- message.includes("server error")
- ) {
+ if (
+ normalizedMessage.includes("500") ||
+ normalizedMessage.includes("502") ||
+ normalizedMessage.includes("503") ||
+ normalizedMessage.includes("504") ||
+ normalizedMessage.includes("server error")
+ ) {
return new Error(
"LiteLLM proxy server error. Please check the proxy server logs for more details.",
);
}
- return new Error(`LiteLLM Error: ${message}`);
+ return new Error(`LiteLLM Error: ${rawMessage}`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const message = | |
| typeof errorRecord?.message === "string" | |
| ? errorRecord.message | |
| : "Unknown error"; | |
| if ( | |
| errorRecord?.name === "TimeoutError" || | |
| (typeof errorRecord?.message === "string" && | |
| errorRecord.message.includes("Timeout")) | |
| message.includes("ECONNREFUSED") || | |
| message.includes("Failed to fetch") || | |
| message.includes("ECONNRESET") || | |
| message.includes("ENOTFOUND") | |
| ) { | |
| return new Error( | |
| `LiteLLM request timed out: ${errorRecord?.message || "Unknown timeout"}`, | |
| `LiteLLM proxy server not available. Please start the LiteLLM proxy server at ${ | |
| process.env.LITELLM_BASE_URL || "http://localhost:4000" | |
| }`, | |
| ); | |
| } | |
| if (typeof errorRecord?.message === "string") { | |
| if ( | |
| errorRecord.message.includes("ECONNREFUSED") || | |
| errorRecord.message.includes("Failed to fetch") | |
| ) { | |
| return new Error( | |
| "LiteLLM proxy server not available. Please start the LiteLLM proxy server at " + | |
| `${process.env.LITELLM_BASE_URL || "http://localhost:4000"}`, | |
| ); | |
| } | |
| if ( | |
| errorRecord.message.includes("API_KEY_INVALID") || | |
| errorRecord.message.includes("Invalid API key") | |
| ) { | |
| return new Error( | |
| "Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.", | |
| ); | |
| } | |
| if ( | |
| message.includes("API_KEY_INVALID") || | |
| message.includes("Invalid API key") | |
| ) { | |
| return new Error( | |
| "Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.", | |
| ); | |
| } | |
| if (errorRecord.message.includes("rate limit")) { | |
| return new Error( | |
| "LiteLLM rate limit exceeded. Please try again later.", | |
| ); | |
| } | |
| if ( | |
| message.includes("rate limit") || | |
| message.includes("rate_limit_exceeded") || | |
| message.includes("429") | |
| ) { | |
| return new Error("LiteLLM rate limit exceeded. Please try again later."); | |
| } | |
| if ( | |
| errorRecord.message.includes("model") && | |
| errorRecord.message.includes("not found") | |
| ) { | |
| return new Error( | |
| `Model '${this.modelName}' not available in LiteLLM proxy. ` + | |
| "Please check your LiteLLM configuration and ensure the model is configured.", | |
| ); | |
| } | |
| if ( | |
| message.includes("500") || | |
| message.includes("502") || | |
| message.includes("503") || | |
| message.includes("504") || | |
| message.includes("server error") | |
| ) { | |
| return new Error( | |
| "LiteLLM proxy server error. Please check the proxy server logs for more details.", | |
| ); | |
| } | |
| return new Error( | |
| `LiteLLM error: ${errorRecord?.message || "Unknown error"}`, | |
| ); | |
| return new Error(`LiteLLM Error: ${message}`); | |
| } | |
| const rawMessage = | |
| typeof errorRecord?.message === "string" | |
| ? errorRecord.message | |
| : "Unknown error"; | |
| const normalizedMessage = rawMessage.toLowerCase(); | |
| if ( | |
| normalizedMessage.includes("econnrefused") || | |
| normalizedMessage.includes("failed to fetch") || | |
| normalizedMessage.includes("econnreset") || | |
| normalizedMessage.includes("enotfound") | |
| ) { | |
| return new Error( | |
| `LiteLLM proxy server not available. Please start the LiteLLM proxy server at ${ | |
| process.env.LITELLM_BASE_URL || "http://localhost:4000" | |
| }`, | |
| ); | |
| } | |
| if ( | |
| normalizedMessage.includes("api_key_invalid") || | |
| normalizedMessage.includes("invalid api key") | |
| ) { | |
| return new Error( | |
| "Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.", | |
| ); | |
| } | |
| if ( | |
| normalizedMessage.includes("rate limit") || | |
| normalizedMessage.includes("rate_limit_exceeded") || | |
| normalizedMessage.includes("429") | |
| ) { | |
| return new Error("LiteLLM rate limit exceeded. Please try again later."); | |
| } | |
| if ( | |
| normalizedMessage.includes("500") || | |
| normalizedMessage.includes("502") || | |
| normalizedMessage.includes("503") || | |
| normalizedMessage.includes("504") || | |
| normalizedMessage.includes("server error") | |
| ) { | |
| return new Error( | |
| "LiteLLM proxy server error. Please check the proxy server logs for more details.", | |
| ); | |
| } | |
| return new Error(`LiteLLM Error: ${rawMessage}`); |
🤖 Prompt for AI Agents
In src/lib/providers/litellm.ts around lines 106 to 154, the code performs
substring matches against message but leaves message in original casing causing
case-sensitive misses; create a lowercase copy once (e.g., const lower =
message.toLowerCase()) and use lower.includes(...) for all subsequent checks
(ECONNREFUSED/failed fetch/invalid key/rate limit/server error checks) while
preserving and using the original message string in the final returned Error
wrapper.
There was a problem hiding this comment.
Pull Request Overview
This PR refactors error handling across AI provider classes to improve error message clarity and consistency. The changes simplify the error formatting logic while providing more specific, user-friendly error messages.
Key changes:
- Standardized error handling patterns across all provider classes
- Added comprehensive error detection for rate limits, connection issues, and server errors
- Simplified message extraction logic by moving it earlier in error handling functions
- Enhanced provider-specific error messages with consistent "Provider Error:" formatting
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/providers/openAI.ts | Added comprehensive error handling for connection and server errors with improved rate limit detection |
| src/lib/providers/mistral.ts | Enhanced error handling with model-not-found detection and consistent error formatting |
| src/lib/providers/litellm.ts | Simplified error message extraction and improved server error handling |
| src/lib/providers/googleVertex.ts | Added connection and server error handling to existing permission logic |
| src/lib/providers/googleAiStudio.ts | Reorganized error handling order and added comprehensive error type detection |
| src/lib/providers/azureOpenai.ts | Restructured error handling with improved message extraction and consistent patterns |
| src/lib/providers/anthropic.ts | Simplified error message extraction logic and improved code readability |
| src/lib/providers/amazonBedrock.ts | Added rate limit, connection, and server error handling to existing error logic |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| if (message.includes("model") && message.includes("not found")) { | ||
| return new Error( | ||
| `Mistral model '${this.modelName}' not found.\n\nSuggestions:\n1. Check model name spelling\n2. Ensure you have access to the model`, |
There was a problem hiding this comment.
[nitpick] The multiline error message with embedded newlines reduces readability. Consider using template literals with proper line breaks or extracting this to a constant.
| `Mistral model '${this.modelName}' not found.\n\nSuggestions:\n1. Check model name spelling\n2. Ensure you have access to the model`, | |
| `Mistral model '${this.modelName}' not found. |
|
|
||
| if ( |
There was a problem hiding this comment.
The timeout error handling that was removed (lines checking for TimeoutError and Timeout in message) should be preserved as timeouts are a distinct error condition that users should be able to identify.
| if ( | |
| // Check for timeout errors in the message | |
| if ( | |
| message.includes("TimeoutError") || | |
| message.toLowerCase().includes("timeout") || | |
| message.toLowerCase().includes("timed out") | |
| ) { | |
| return new Error(`LiteLLM request timed out: ${message}`); | |
| } | |
| if ( |
|
Closing as part of project audit (2026-03-29). The feature this PR targets was implemented via a different approach in a later release. See docs/project-audit-2026-03-29.md for details. |
Pull Request
Description
Type of Change
Related Issues
Changes Made
AI Provider Impact
Component Impact
Testing
Test Environment
Performance Impact
Breaking Changes
Screenshots/Demo
Checklist
Additional Notes
Summary by CodeRabbit